﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.ComponentModel;
using System.Diagnostics;
using System.ServiceProcess;
using System.Reflection;

namespace PSE.Deployment.Util
{
    public static class WindowsService
    {
        //private const int SC_MANAGER_CREATE_SERVICE = 0x0002;
        //private const int SERVICE_WIN32_OWN_PROCESS = 0x00000010;
        //private const int SERVICE_DEMAND_START = 0x00000003;
        //private const int SERVICE_ERROR_NORMAL = 0x00000001;
        //private const int STANDARD_RIGHTS_REQUIRED = 0xF0000;
        //private const int SERVICE_QUERY_CONFIG = 0x0001;
        //private const int SERVICE_CHANGE_CONFIG = 0x0002;
        //private const int SERVICE_QUERY_STATUS = 0x0004;
        //private const int SERVICE_ENUMERATE_DEPENDENTS = 0x0008;
        //private const int SERVICE_START = 0x0010;
        //private const int SERVICE_STOP = 0x0020;
        //private const int SERVICE_PAUSE_CONTINUE = 0x0040;
        //private const int SERVICE_INTERROGATE = 0x0080;
        //private const int SERVICE_USER_DEFINED_CONTROL = 0x0100;
        //private const int SERVICE_DELETE = 0x10000;
        //private const int SERVICE_AUTO_START = 0x00000002;
        //private const int GENERIC_WRITE = 0x40000000;

        //private const int SERVICE_ALL_ACCESS = (STANDARD_RIGHTS_REQUIRED |
        //                SERVICE_QUERY_CONFIG |
        //                SERVICE_CHANGE_CONFIG |
        //                SERVICE_QUERY_STATUS |
        //                SERVICE_ENUMERATE_DEPENDENTS |
        //                SERVICE_START |
        //                SERVICE_STOP |
        //                SERVICE_PAUSE_CONTINUE |
        //                SERVICE_INTERROGATE |
        //                SERVICE_USER_DEFINED_CONTROL);

        private enum SCManagerAccess : int
        {
            GENERIC_ALL = 0x10000000
        }

        private enum ServiceAccess : int
        {
            QUERY_CONFIG = 0x1,
            CHANGE_CONFIG = 0x2,
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct QueryServiceConfigStruct
        {
            public int serviceType;
            public int startType;
            public int errorControl;
            public IntPtr binaryPathName;
            public IntPtr loadOrderGroup;
            public int tagID;
            public IntPtr dependencies;
            public IntPtr startName;
            public IntPtr displayName;
        }

        public class ServiceInfo
        {
            public int serviceType;
            public int startType;
            public int errorControl;
            public string binaryPathName;
            public string loadOrderGroup;
            public int tagID;
            public string dependencies;
            public string startName;
            public string displayName;
        }

        [DllImport("advapi32.dll")]
        private static extern IntPtr OpenSCManager(string lpMachineName, string lpSCDB, int scParameter);

        //[DllImport("advapi32.dll")]
        //private static extern IntPtr CreateService(IntPtr SC_HANDLE, string lpSvcName, string lpDisplayName,
        //int dwDesiredAccess, int dwServiceType, int dwStartType, int dwErrorControl, string lpPathName,
        //string lpLoadOrderGroup, int lpdwTagId, string lpDependencies, string lpServiceStartName, string lpPassword);

        [DllImport("advapi32.dll", SetLastError = true)]
        private static extern IntPtr OpenService(IntPtr SCHANDLE, string lpSvcName, int dwNumServiceArgs);

        //[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
        //private static extern int ChangeServiceConfig(IntPtr service, int serviceType, int startType, int errorControl, [MarshalAs(UnmanagedType.LPTStr)]string binaryPathName,
        //                                                [MarshalAs(UnmanagedType.LPTStr)] string loadOrderGroup, IntPtr tagID, [MarshalAs(UnmanagedType.LPTStr)] string dependencies,
        //                                                [MarshalAs(UnmanagedType.LPTStr)] string startName, [MarshalAs(UnmanagedType.LPTStr)] string password, [MarshalAs(UnmanagedType.LPTStr)] string displayName);
        
        [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
        private static extern int QueryServiceConfig(IntPtr service, IntPtr queryServiceConfig, int bufferSize, ref int bytesNeeded);

        //[DllImport("advapi32.dll")]
        //private static extern int DeleteService(IntPtr SVHANDLE);

        [DllImport("advapi32.dll")]
        private static extern void CloseServiceHandle(IntPtr SCHANDLE);

        private static StringBuilder messageBuilder;

        public static bool InstallService(string serviceName, string fileName)
        {
            string m = string.Empty;
            return InstallService(serviceName, fileName, true, out m);
        }

        public static bool InstallService(string serviceName, string fileName, out string message)
        {
            return InstallService(serviceName, fileName, true, out message);
        }

        public static bool UninstallService(string serviceName, string fileName)
        {
            string m = string.Empty;
            return InstallService(serviceName, fileName, false, out m);
        }

        public static bool UninstallService(string serviceName, string fileName, out string message)
        {
            return InstallService(serviceName, fileName, false, out message);
        }

        private static bool InstallService(string serviceName, string fileName, bool install, out string message)
        {
            bool sucess = false;
            string frameworkFolder = "Framework";
            ProcessorArchitecture processorArchitecture = AssemblyName.GetAssemblyName(Assembly.GetEntryAssembly().Location).ProcessorArchitecture;

            if (processorArchitecture == ProcessorArchitecture.IA64 || processorArchitecture == ProcessorArchitecture.Amd64)
            {
                frameworkFolder = "Framework64";
            }

            Process p = new Process();
            p.StartInfo.FileName = Environment.GetEnvironmentVariable("windir") + string.Format(@"\Microsoft.NET\{0}\v2.0.50727\installutil.exe", frameworkFolder);

            if (install)
                p.StartInfo.Arguments = string.Format("/i /LogFile=\"\" \"{0}\"", fileName);
            else
                p.StartInfo.Arguments = string.Format("/u /LogFile=\"\" \"{0}\"", fileName);

            p.StartInfo.CreateNoWindow = true;
            p.StartInfo.UseShellExecute = false;
            p.StartInfo.RedirectStandardError = true;
            p.StartInfo.RedirectStandardOutput = true;

            messageBuilder = new StringBuilder();

            p.ErrorDataReceived += new DataReceivedEventHandler(p_ErrorDataReceived);
            p.OutputDataReceived += new DataReceivedEventHandler(p_OutputDataReceived);

            p.Start();

            p.BeginOutputReadLine();
            p.BeginErrorReadLine();

            p.WaitForExit();

            message = messageBuilder.ToString();

            if (!install && p.ExitCode != 0)
            {
                PSE.Deployment.Util.Windows.RunProcess("cmd.exe", string.Format("/c sc delete \"{0}\"", serviceName));
                sucess = (WindowsService.GetServiceInfo(serviceName) == null);
            }
            else
            {
                sucess = (p.ExitCode == 0);
            }

            return sucess;
        }

        static void p_OutputDataReceived(object sender, DataReceivedEventArgs e)
        {
            messageBuilder.AppendLine(e.Data);
        }

        static void p_ErrorDataReceived(object sender, DataReceivedEventArgs e)
        {

        }

        public static ServiceInfo GetServiceInfo(string ServiceName)
        {
            IntPtr scManager = OpenSCManager(".", null, (int)SCManagerAccess.GENERIC_ALL);

            if (scManager.ToInt32() <= 0)
                return null;

            IntPtr service = OpenService(scManager, ServiceName, (int)ServiceAccess.QUERY_CONFIG);

            if (service.ToInt32() <= 0)
                return null;

            int bytesNeeded = 5;

            QueryServiceConfigStruct qscs = new QueryServiceConfigStruct();
            IntPtr qscPtr = Marshal.AllocCoTaskMem(0);

            int retCode = QueryServiceConfig(service, qscPtr, 0, ref bytesNeeded);

            if (retCode == 0 && bytesNeeded == 0)
            {
                return null;
            }
            else
            {
                qscPtr = Marshal.AllocCoTaskMem(bytesNeeded);

                retCode = QueryServiceConfig(service, qscPtr, bytesNeeded, ref bytesNeeded);

                if (retCode == 0)
                    return null;

                qscs.binaryPathName = IntPtr.Zero;
                qscs.dependencies = IntPtr.Zero;
                qscs.displayName = IntPtr.Zero;
                qscs.loadOrderGroup = IntPtr.Zero;
                qscs.startName = IntPtr.Zero;

                qscs = (QueryServiceConfigStruct)
                Marshal.PtrToStructure(qscPtr, new QueryServiceConfigStruct().GetType());
            }

            ServiceInfo serviceInfo = new ServiceInfo();
            serviceInfo.binaryPathName = Marshal.PtrToStringAuto(qscs.binaryPathName);
            serviceInfo.dependencies = Marshal.PtrToStringAuto(qscs.dependencies);
            serviceInfo.displayName = Marshal.PtrToStringAuto(qscs.displayName);
            serviceInfo.loadOrderGroup = Marshal.PtrToStringAuto(qscs.loadOrderGroup);
            serviceInfo.startName = Marshal.PtrToStringAuto(qscs.startName);

            serviceInfo.errorControl = qscs.errorControl;
            serviceInfo.serviceType = qscs.serviceType;
            serviceInfo.startType = qscs.startType;
            serviceInfo.tagID = qscs.tagID;

            Marshal.FreeCoTaskMem(qscPtr);
            CloseServiceHandle(scManager);
            CloseServiceHandle(service);

            return serviceInfo;
        }

        public static int ChangeStartType(string serviceName, ServiceStartMode mode)
        {
            string start = string.Empty;

            switch (mode)
            {
                case ServiceStartMode.Automatic:
                    start = "auto";
                    break;
                case ServiceStartMode.Disabled:
                    start = "disabled";
                    break;
                case ServiceStartMode.Manual:
                    start = "demand";
                    break;                
            }

            return Windows.RunProcess("cmd.exe", string.Format("/c sc config {0} start= {1}", serviceName, start));
        }
    }
}
